import re
import unreal

#helper method to list python type info
def list_unreal_types(search_criteria=''):
    type_list = sorted(dir(unreal))

    if search_criteria and search_criteria.strip():
        r = re.compile(search_criteria)
        type_list = list(filter(r.match,type_list))

    for type in type_list:
        print(type)

#helper method to list types related to simpygon types
def list_functions():
    for n in sorted(dir(unreal.SimplygonBlueprintLibrary)):
        print(n)

    for n in sorted(dir(unreal.SimplygonStandinPipeline)):
        print(n)

    for n in sorted(dir(unreal.StandInMeshActor)):
         print(n)

#get all actors based on a criteria
def get_all_actor(use_selection = False, actor_class = None, actor_tag = None):
    if use_selection:
        selected_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
        class_actors = selected_actors
        if actor_class:
            class_actors = [x for x in selected_actors if cast(x,actor_class)]
        tag_actors = class_actors
        if actor_tag:
            tag_actors = [x for x in selected_actors if x.actor_has_tag(actor_tag)]
        return [x for x in tag_actors]

    elif actor_class:
        actors = unreal.GameplayStatics.get_all_actors_of_class(unreal.EditorLevelLibrary.get_editor_world(),actor_class)
        tag_actors = actors
        if actor_tag:
            tag_actors = [x for x in actors if x.actor_has_tag(actor_tag)]
        return [x for x in tag_actors]

    elif actor_tag:
        tag_actors = unreal.GameplayStatics.get_all_actors_of_class(unreal.EditorLevelLibrary.get_editor_world(),actor_tag)
        return [x for x in tag_actors]

    else:
        actors = unreal.GameplayStatics.get_all_actors_of_class(unreal.EditorLevelLibrary.get_editor_world(),unreal.Actor)
        return [x for x in actors]

def get_actors_by_name(selected_actors, name):
    r = re.compile(name+'*')
    result = [x for x in selected_actors if r.match(x.get_name())]
    return [x for x in result]


#used to cast objects
def cast(obj_to_cast, object_class):
    try:
        return object_class.cast(obj_to_cast)
    except:
        return None

#simple usage of how to build all standin meshes in a level
def generate_standins_in_level():
    unreal.SimplygonBlueprintLibrary.build_standin_meshes()


#create a default remeshing pipeline and update the on screen size
def create_remeshing_pipeline(screen_size = 300):
    remeshing_pipeline = unreal.RemeshingPipeline()
    remeshing_pipeline_settings = remeshing_pipeline.get_editor_property('settings')
    remeshing_settings = remeshing_pipeline_settings.get_editor_property('remeshing_settings')
    remeshing_settings.set_editor_property('on_screen_size',screen_size)
    return remeshing_pipeline

#create a default far pipeline
def create_far_pipeline(screen_size = 300):
    far_pipeline = unreal.StandinFarPipeline()
    pipeline_settings = far_pipeline.get_editor_property('settings')
    remeshing_settings = pipeline_settings.get_editor_property('remeshing_settings')
    remeshing_settings.set_editor_property('on_screen_size',screen_size)
    return far_pipeline

#create a new pipline with geometry culling
def create_near_pipeline():
    near_pipeline = unreal.StandinNearPipeline()
    pipeline_settings = near_pipeline.get_editor_property('settings')
    aggregation_settings = pipeline_settings.get_editor_property('aggregation_settings')
    aggregation_settings.set_editor_property('enable_geometry_culling', True)
      
    #enable volumetic culling
    geometry_cull_settings = pipeline_settings.get_editor_property('geometry_culling_settings')
    geometry_cull_settings.set_editor_property('use_clipping_geometry', True)
   
    #fetch material caster properites
    material_prop_caster = pipeline_settings.get_editor_property('material_property_casters')

    #create and add new casters
    base_color_caster = unreal.BaseColorCaster()
    specular_caster = unreal.SpecualarCaster()
    normal_caster = unreal.NormalCaster()
    material_prop_caster.append(base_color_caster)
    material_prop_caster.append(specular_caster)
    material_prop_caster.append(normal_caster)
    return near_pipeline

#create a generic asset type. we use this for creating a LODRecipe asset
def create_generic_asset(asset_path='', unique_name=True, asset_class=None, asset_factory=None):
    if unique_name:
        asset_path,asset_name = unreal.AssetToolsHelpers.get_asset_tools().create_unique_asset_name(base_package_name=asset_path,suffix='')
    if not unreal.EditorAssetLibrary.does_asset_exist(asset_path=asset_path):
        path = asset_path.rsplit('/',1)[0]
        name = asset_path.rsplit('/',1)[1]
        return unreal.AssetToolsHelpers.get_asset_tools().create_asset(asset_name=name,package_path=path,asset_class=asset_class,factory=asset_factory)
    
    return unreal.load_asset(asset_path)


#create a lod recipe
def create_lod_recipe(base_path='', name = ''):

    if not base_path:
        base_path = '/Game/LODRecipes/'
    
    if not name:
        name = 'DefaultRecipe'

    return create_generic_asset(asset_path=base_path+name, asset_class=unreal.LODRecipe, asset_factory = unreal.LODRecipeFactory())


#simple example showcasing how to create to setup a lod workflow using lod recipe in python
def create_lod_recipe_and_build_example(recipe_name , asset_path):
    #create new recipe
    new_lod_chain = create_lod_recipe(recipe_name)
    
    #set number of LODs that you want to generate (All lods default to a ReductionPipline)
    new_lod_chain.set_editor_property('num_lo_ds', 5)

    #fetch the per_lod_pipeline_settings
    pipelines = new_lod_chain.get_editor_property('per_lod_pipeline_settings')
    
    #list current pipelines
    for pipeline in pipelines:
        print(pipeline)

    #fetch the pipline_settings object for LOD 1
    reduction_pipeline_settings = pipelines[1].get_editor_property('settings')

    #fetch reduction_settings
    reduction_settings = reduction_pipeline_settings.get_editor_property('reduction_settings')

    #update the triangle ratio to 88%
    reduction_settings.set_editor_property('reduction_target_triangle_ratio', 0.88)

    #replace LOD4 with a remeshing pipline with on screen size of 30
    pipelines[4] = create_remeshing_pipeline(30)

    #fetch the asset you want to link with the recipe
    asset = unreal.load_asset(asset_path)

    #assign recipe to asset using helper method provided by the SimplygonBlueprintLibrary
    unreal.SimplygonBlueprintLibrary.assign_recipe_to_asset(new_lod_chain,asset)

    #build the LODrecipe for the asset and save ditry packages after processing is done
    unreal.SimplygonBlueprintLibrary.build_lod_recipe(asset)
    unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True,True)


# example method to showcase how to setup far standin using python
def create_far_standin_from_selection_and_build_example():
    
    #get all actors tagged with far standiin
    selected_actors = get_all_actor(actor_class=unreal.StaticMeshActor,actor_tag='FarStandin')

    #create standin actor from selected actor
    standin_actor =unreal.SimplygonBlueprintLibrary.create_standin_actor_from_actors(selected_actors)

    #setup and assign far pipeline to the standin actor and modify on screen size
    far_pipeline = create_far_pipeline(150)
    standin_actor.set_editor_property("pipeline", unreal.SimplygonStandinPipeline.cast(far_pipeline))

    #build the standin mesh
    unreal.SimplygonBlueprintLibrary.build_standin_meshes()
    unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True,True)


# example method to showcase how to setup far standin using python
def create_near_standin_from_selection_and_build_example(clipping_geo_name):
    #get all actors tagged with near standiin
    selected_actors = get_all_actor(actor_class=unreal.StaticMeshActor,actor_tag='NearStandin')
    clipping_meshes = get_actors_by_name(selected_actors,clipping_geo_name)

    #set clipping geometry by adding simplygon userdata to actor
    for actor in clipping_meshes:
        unreal.SimplygonBlueprintLibrary.add_simplygon_user_data(actor, unreal.SimplygonMetaTagType.CLIPPING_GEOMETRY)

    #create standin from selected actors
    standin_actor =unreal.SimplygonBlueprintLibrary.create_standin_actor_from_actors(selected_actors)

    #setup a near pipeline which uses 
    near_pipeline = create_near_pipeline()
    
    standin_actor.set_editor_property("pipeline", unreal.SimplygonStandinPipeline.cast(near_pipeline))
    unreal.SimplygonBlueprintLibrary.build_standin_meshes()
    unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True,True)


# Helper method to apply a recipes to all static meshes in a folder
def assign_recipe_to_static_meshes_in_path(lod_recipe_path,package_path, recursive = False):
    lod_recipe_asset = unreal.load_asset(lod_recipe_path)
    asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()
    assets_list = asset_registry.get_assets_by_path(package_path, recursive)
    static_meshes = [sm for sm in assets_list if sm.asset_class == 'StaticMesh']
    for sm in static_meshes:
        unreal.SimplygonBlueprintLibrary.assign_recipe_to_asset(lod_recipe_asset,sm.get_asset())
    unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True,True)

# Helper method to apply a recipes to all skeletal meshes in a folder
def assign_recipe_to_skeletal_meshes_in_path(lod_recipe_path,package_path, recursive = False):
    lod_recipe_asset = unreal.load_asset(lod_recipe_path)
    asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()
    assets_list = asset_registry.get_assets_by_path(package_path, recursive)
    skeletal_meshes = [sm for sm in assets_list if sm.asset_class == 'SkeletalMesh']
    for sm in skeletal_meshes:
        unreal.SimplygonBlueprintLibrary.assign_recipe_to_asset(lod_recipe_asset,sm.get_asset())
    unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True,True)